<template>
  <div v-if="images.length" class="swiper">
    <div
      class="container relative"
      ref="container"
      :style="`--width: ${width}px; --height: ${slide_list[0].height}px`"
    >
      <div class="swiper-wrapper">
        <div
          v-for="slide in slide_list"
          class="swiper-item"
          :style="`transform: translateX(${slide.offset}px)`"
        >
          <img
            :src="slide.url"
            :style="`width: ${slide.width}px; height: ${slide.height}px;`"
            draggable="false"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { usePointerSwipe } from "@vueuse/core";
import a1 from "./images/1.jpeg";
import a2 from "./images/2.jpeg";
import a3 from "./images/3.jpeg";
import a4 from "./images/4.jpeg";
import a5 from "./images/5.jpeg";

// 原始图片数据
type Image = {
  url: string;
  w: number;
  h: number;
};

// 扩展图片信息
type SlideImage = Image & {
  width: number; // 显示宽度
  height: number; // 显示高度
  offset: number;
};

type Size = {
  width: number;
  height: number;
};

function getNext(relative: number, offset: number) {
  let next: number;
  // 左滑为负值, 切换到 next
  // 右滑为正值, 切换到 previous
  if (offset === 0) next = relative;
  else if (offset < 0) next = relative + 1;
  else next = relative - 1;
  const length = images.value.length;
  return next >= length ? length - 1 : next < 0 ? 0 : next;
}

function getViewHeight(
  relative: number,
  offset: number,
  w: number,
  list: Image[]
): number {
  const next = getNext(relative, offset);
  // 默认状态
  const currentImage = list[relative];
  const currentHeight = w / (currentImage.w / currentImage.h);
  if (next === relative) return currentHeight;
  // 滑动状态
  const nextImage = list[next];
  const nextHeight = w / (nextImage.w / nextImage.h);
  const p = Math.abs(offset / w);
  const deltaHeight = nextHeight - currentHeight;
  return currentHeight + deltaHeight * (p > 1 ? 1 : p < 0 ? 0 : p);
}

function getViewSizeUseHeight(image: Image, h: number): Size {
  const height = h;
  const width = height * (image.w / image.h);
  return {
    width,
    height,
  };
}

function calcOffsetX(relative: number, offsetX: number, list: Image[]) {
  return offsetX;
  const oh = getViewHeight(relative, 0, width, images.value);
  const next = getNext(relative, offsetX);
  const ns = getViewSizeUseHeight(list[next], oh);
  const p = Math.abs(offsetX / width);
  const dir = next > relative ? -1 : 1;
  return ns.width * p * dir;
}

function getOffset(
  list: SlideImage[],
  relative: number,
  index: number,
  offsetX: number
) {
  let d = 1;
  let offset = 0;
  let start = relative;
  let end = index;
  if (relative > index) {
    start = index;
    end = relative;
    d = -1;
  }
  while (start < end) {
    offset += list[start++].width * d;
  }
  return offset + offsetX;
}

function ensureSelectedCentered() {
  const offset = translateX.value;
  translateX.value = 0;
  const p = Math.abs(offset / width);
  if (p < boundary) return;
  const relative = selected.value;
  const next = getNext(relative, offset);
  if (next == relative) return;
  selected.value = next;
}

// 当前索引
const selected = ref(2);
// 偏移量
const translateX = ref(0);

const container = ref(null);
const { distanceX } = usePointerSwipe(container, {
  onSwipeStart() {},
  onSwipe() {
    // move
  },
  onSwipeEnd() {
    ensureSelectedCentered();
  },
});

watch(distanceX, (x) => {
  translateX.value = -x;
});

// 滑动时, 切换边界
const boundary = 0.5;
const images = ref<Image[]>([]);
// 屏幕窗口宽度
const width = Math.min(480, window.innerWidth - 48);
const slide_list = computed<SlideImage[]>(() => {
  const relative = selected.value;
  const list = images.value;
  const height = getViewHeight(relative, translateX.value, width, list);
  const slides = list.map((image) => {
    return {
      ...image,
      ...getViewSizeUseHeight(image, height),
      offset: 0,
    };
  });
  const offsetX = calcOffsetX(relative, translateX.value, list);
  return slides.map((slide, index) => {
    return {
      ...slide,
      offset: getOffset(slides, relative, index, offsetX),
    };
  });
});

// 准备数据以及初始化
function load(url: string): Promise<Image> {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = function () {
      const w = image.naturalWidth;
      const h = image.naturalHeight;
      resolve({ w, h, url });
    };
    image.src = url;
  });
}

async function initImageList(images: string[]) {
  return Promise.all(images.map((url) => load(url)));
}

initImageList([a1, a1, a2, a3, a4, a5]).then((res) => {
  images.value = res;
});
</script>

<style scoped>
.container {
  width: var(--width);
  height: var(--height);
  margin: 16px auto;
  touch-action: initial;
  cursor: grab;
  overflow: hidden;
}

.swiper-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
}
.swiper-item {
  position: absolute;
  transition: all 0.05s;
}
.swiper-item img {
  display: block;
  max-width: initial;
}
</style>

<style>
body {
  overflow: hidden;
}
</style>
